package org.example.service;

import cn.hutool.extra.template.TemplateException;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.example.exception.CodeGenerationException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.*;
import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.*;

/**
 * @author Zhujingwei
 * @description
 * @DateTime: 2025/6/14 17:38
 */
@Service
@Slf4j
public class GeneratorService {
    /**
     * 模板文件所在的目录
     */
    private static final String TEMPLATE_DIR = "src/main/resources/templates";
    /**
     * 代码生成的包名，即代码生成后所在的位置
     */
    private static final String PACKAGE_NAME = "org.generator";
    /**
     * Mapper文件生成后所在的目录
     */
    private static final String MAPPER_PACKAGE_NAME = "src/main/resources/mapper";
    /**
     * 公共字段
     */
    private static final List<String> COMMON_FIELDS = Arrays.asList("id", "creator","create_time","modifier", "update_time");
    /**
     * 数据库链接
     */
    @Value("${spring.datasource.url}")
    private String dbUrl;
    /**
     * 数据库用户名
     */
    @Value("${spring.datasource.username:root}")
    private String userName;
    /**
     * 数据库密码
     */
    @Value("${spring.datasource.password:root}")
    private String password;

    private String entity = "entity";

    private String service = "service";

    private String controller = "controller";

    private String mapper = "mapper";

    private String mybatis = "mybatis";

    /**
     * 根据表名生成代码，代码生成器入口
     * @param tableName
     */
    public Void generateCode(String tableName) {
        // 获取表的注释
        String tableComment = getTableComment(tableName);
        // 从数据库中获取列信息
        List<ColumnInfo> columns = getColumnsFromDatabase(tableName);
        //表名转换成驼峰实体名
        String entityName = camelCase(tableName);
        // 创建数据模型
        Map<String, Object> dataModel = new HashMap<>();
        dataModel.put("packageName", PACKAGE_NAME);
        dataModel.put("tableComment", tableComment);
        dataModel.put("entityName", entityName);
        dataModel.put("columns", columns);
        dataModel.put("commonFields", COMMON_FIELDS);
        dataModel.put("tableName", tableName);
        // 获取当前时间并添加到数据模型
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String currentTime = sdf.format(new Date());
        dataModel.put("currentTime", currentTime);
        // 创建 FreeMarker 配置
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
        try {
            cfg.setDirectoryForTemplateLoading(new File(TEMPLATE_DIR));
            // 存储生成的文件内容
            Map<String, String> fileContents = new HashMap<>();
            fileContents.put(entity, generateContent(cfg, "entity.ftl", dataModel));
            fileContents.put(service, generateContent(cfg, "service.ftl", dataModel));
            fileContents.put("serviceImpl", generateContent(cfg, "serviceImpl.ftl", dataModel));
            fileContents.put(controller, generateContent(cfg, "controller.ftl", dataModel));
            fileContents.put(mapper, generateContent(cfg, "mapper.ftl", dataModel));
            fileContents.put("mapperXml", generateContent(cfg, "mapperXml.ftl", dataModel));
            // 所有内容生成成功后，统一写入文件
            writeFiles(fileContents, entityName);
        } catch (IOException | TemplateException e) {
            log.error("代码生成过程中出现异常，未生成文件: ", e);
        } catch (freemarker.template.TemplateException e) {
            throw new CodeGenerationException("代码生成过程中出现异常，未生成文件: ", e);
        }
        return null;
    }

    /**
     * 生成文件内容
     *
     * @param cfg FreeMarker 配置
     * @param templateName 模板文件名
     * @param dataModel 数据模型
     * @return 生成的文件内容
     * @throws IOException 如果发生 I/O 错误
     * @throws TemplateException 如果 FreeMarker 模板处理过程中发生错误
     */
    private String generateContent(Configuration cfg, String templateName, Map<String, Object> dataModel)
            throws IOException, TemplateException, freemarker.template.TemplateException {
        Template template = cfg.getTemplate(templateName);
        StringWriter writer = new StringWriter();
        template.process(dataModel, writer);
        return writer.toString();
    }

    /**
     * 将生成的文件内容写入文件
     *
     * @param fileContents 生成的文件内容
     * @param entityName 实体名称
     * @throws IOException 如果发生 I/O 错误
     */
    private void writeFiles(Map<String, String> fileContents, String entityName) {
        try {
            writeFile(fileContents.get(entity), entity, entityName + ".java");
            writeFile(fileContents.get(service), service, entityName + "Service.java");
            writeFile(fileContents.get("serviceImpl"), "service.impl", entityName + "ServiceImpl.java");
            writeFile(fileContents.get(controller), controller, entityName + "Controller.java");
            writeFile(fileContents.get(mapper), mapper, entityName + "Mapper.java");
            writeFile(fileContents.get("mapperXml"), mybatis, entityName + "Mapper.xml");
        } catch (IOException e) {
            log.error("文件写入过程中出现异常: {}",e.getMessage());
        }
    }

    /**
     * 将生成的文件内容写入文件
     *
     * @param content 生成的文件内容
     * @param subPackage 子包名
     * @param fileName 文件名
     * @throws IOException 如果发生 I/O 错误
     */
    private void writeFile(String content, String subPackage, String fileName)
            throws IOException {
        String packagePath;
        if (mybatis.equals(subPackage)){
            packagePath = MAPPER_PACKAGE_NAME;
        } else if ("service.impl".equals(subPackage)) {
            packagePath = PACKAGE_NAME.replace('.', File.separatorChar) + File.separator + service + File.separator + "impl";
        } else {
            packagePath = GeneratorService.PACKAGE_NAME.replace('.', File.separatorChar) + File.separator + subPackage;
        }
        File fileDir = new File("src/main/java/" + packagePath);
        if (mybatis.equals(subPackage)) {
            fileDir = new File(packagePath);
        }
        if (!fileDir.exists()) {
            fileDir.mkdirs();
        }
        File file = new File(fileDir, fileName);
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
            writer.write(content);
            log.info("File " + file.getAbsolutePath() + " generated successfully.");
        }
    }

    /**
     * 将字符串转换为驼峰命名法
     *
     * @param str 要转换的字符串
     * @return 转换后的驼峰命名法字符串
     */
    private String camelCase(String str) {
        StringBuilder result = new StringBuilder();
        String[] parts = str.split("_");
        for (String part : parts) {
            result.append(Character.toUpperCase(part.charAt(0))).append(part.substring(1).toLowerCase());
        }
        return result.toString();
    }

    /**
     * 将字符串从下划线命名法转换为驼峰命名法
     *
     * @param str 要转换的字符串
     * @return 转换后的驼峰命名法字符串
     */
    private String underlineToCamel(String str) {
        StringBuilder result = new StringBuilder();
        boolean nextUpperCase = false;
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (c == '_') {
                nextUpperCase = true;
            } else {
                if (nextUpperCase) {
                    result.append(Character.toUpperCase(c));
                    nextUpperCase = false;
                } else {
                    result.append(c);
                }
            }
        }
        return result.toString();
    }

    /**
     * 从数据库中获取列信息
     *
     * @param tableName 表名
     * @return 列信息列表
     */
    private List<ColumnInfo> getColumnsFromDatabase(String tableName) {
        List<ColumnInfo> columns = new ArrayList<>();
        try (Connection conn = DriverManager.getConnection(dbUrl, userName, password);
             Statement stmt = conn.createStatement()) {
            String sql = "SHOW FULL COLUMNS FROM " + tableName;
            ResultSet rs = stmt.executeQuery(sql);
            while (rs.next()) {
                String fieldName = rs.getString("Field");
                String camelCaseFieldName = underlineToCamel(fieldName);
                String type = rs.getString("Type");
                String comment = rs.getString("Comment");
                String javaType = getJavaType(type);
                columns.add(new ColumnInfo(fieldName,camelCaseFieldName, javaType, comment));
            }
        } catch (SQLException e) {
            log.error("Get columns from Database error...");
        }
        return columns;
    }

    /**
     * 获取表的注释
     *
     * @param tableName 表名
     * @return 表的注释
     */
    private String getTableComment(String tableName) {
        String comment = "";
        try (Connection conn = DriverManager.getConnection(dbUrl, userName, password);
             Statement stmt = conn.createStatement()) {
            String sql = "SELECT TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME =" + "'" + tableName + "'";
            ResultSet rs = stmt.executeQuery(sql);
            if (rs.next()) {
                comment = rs.getString("TABLE_COMMENT");
            }
        } catch (SQLException e) {
            log.error("Get table comment error...");
        }
        return comment;
    }

    /**
     * 将 SQL 类型转换为 Java 类型
     *
     * @param sqlType SQL 类型
     * @return 对应的 Java 类型
     */
    private String getJavaType(String sqlType) {
        if (sqlType.startsWith("int") || sqlType.startsWith("smallint") || sqlType.startsWith("tinyint")) {
            return "Integer";
        } else if (sqlType.startsWith("bigint")) {
            return "Long";
        } else if (sqlType.startsWith("varchar") || sqlType.startsWith("char") || sqlType.startsWith("text")) {
            return "String";
        } else if (sqlType.startsWith("date") || sqlType.startsWith("datetime") || sqlType.startsWith("timestamp")) {
            return "LocalDateTime";
        }
        return "Object";
    }

    @Data
    public class ColumnInfo {
        private String fieldName;
        private String camelCaseFieldName;
        private String javaType;
        private String comment;

        public ColumnInfo(String fieldName,String camelCaseFieldName, String javaType, String comment) {
            this.fieldName = fieldName;
            this.camelCaseFieldName = camelCaseFieldName;
            this.javaType = javaType;
            this.comment = comment;
        }
    }

}
